• Steven Ponce
  • About
  • Data Visualizations
  • Projects
  • Resume
  • Email

On this page

  • Original
  • Makeover
  • Steps to Create this Graphic
    • 1. Load Packages & Setup
    • 2. Read in the Data
    • 3. Examine the Data
    • 4. Tidy Data
    • 5. Visualization Parameters
    • 6. Plot
    • 7. Save
    • 8. Session Info
    • 9. GitHub Repository
    • 10. References

Retail Department Performance Analysis: Growth and Volatility

  • Show All Code
  • Hide All Code

  • View Source

Understanding stability patterns and growth trajectories across retail categories (Jan 2024 - Mar 2025)

SWDchallenge
Exercise
Data Visualization
R Programming
2025
An in-depth analysis of retail department performance examining growth rates and volatility across multiple categories. This visualization highlights stability patterns and identifies departments with unpredictable performance, providing valuable insights for inventory management and business planning.
Author

Steven Ponce

Published

March 19, 2025

Modified

May 22, 2025

Update: This post has been updated based on valuable feedback from the Antti Rask. The changes include:

  • Implemented a categorical color palette to create better visual distinction between the three highlighted departments
  • Applied consistent colors across both visualizations to help readers connect departments between charts
  • Created more descriptive titles with colored department names using the ggtext package to enhance immediate comprehension
  • Revised the chart subtitles to better explain the key insights from each visualization
  • Removed redundant axis titles while maintaining necessary labels for clarity
  • Added specific emphasis on Grocery’s unique position as highly volatile despite lower growth
  • Improved the scatter plot to better highlight the relationship between growth rates and stability patterns
  • Enhanced overall visual hierarchy by using color more purposefully throughout the visualization

Original

The goal of this month’s Storytelling with Data exercise is toresist the temptation to show all the data.

Figure 1: Original chart

Additional information can be found HERE

Makeover

Figure 2: Retail department performance visualization with two charts. Top chart shows year-over-year growth trends from Jan 2024 to Mar 2025, highlighting the three most volatile departments (Grocery, Hardware, and Toys) with Grocery showing the most dramatic fluctuations. Bottom chart plots standard deviation against mean growth rate for all departments, revealing that departments with higher volatility (like Grocery) don’t necessarily have the highest average growth rates.

Steps to Create this Graphic

1. Load Packages & Setup

Show code
if (!require("pacman")) install.packages("pacman")
pacman::p_load(
  tidyverse,         # Easily Install and Load the 'Tidyverse'
  ggtext,            # Improved Text Rendering Support for 'ggplot2'
  showtext,          # Using Fonts More Easily in R Graphs
  scales,            # Scale Functions for Visualization
  glue,              # Interpreted String Literals
  here,              # A Simpler Way to Find Your Files
  janitor,           # Simple Tools for Examining and Cleaning Dirty Data
  camcorder,         # Record Your Plot History,
  ggrepel,           # Automatically Position Non-Overlapping Text Labels with 'ggplot2'
  patchwork          # The Composer of Plots # The Composer of Plots # The Composer of Plots
) 

### |- figure size ---- 
gg_record( 
  dir    = here::here("temp_plots"), 
  device = "png",
  width  = 8,
  height = 10,
  units  = "in",
  dpi    = 320)
# Source utility functions
suppressMessages(source(here::here("R/utils/fonts.R")))
source(here::here("R/utils/social_icons.R"))
source(here::here("R/utils/image_utils.R"))
source(here::here("R/themes/base_theme.R"))

2. Read in the Data

Show code
raw_data <- read_csv(
  here::here("data/swdexercise056.csv")
  ) |> 
  clean_names() 

3. Examine the Data

Show code
glimpse(raw_data)

4. Tidy Data

Show code
tidy_data <- raw_data |>
  rename(month = column1, year = column2) |>
  mutate(
    date = as.Date(paste(year, month, "01", sep = "-"), 
                   format = "%Y-%b-%d")
  ) |>
  select(date, everything(), -month, -year) |>
  pivot_longer(
    cols = -date, 
    names_to = "department", 
    values_to = "growth"
  ) |> 
  mutate(
    year_month = format(date, "%b %Y"),
    quarter = paste0("Q", quarter(date)),
    year_quarter = paste(year(date), quarter, sep = "-")
  )

# Compute summary statistics for each department
summary_stats <- tidy_data |>
  group_by(department) |>
  summarise(
    mean_growth = mean(growth, na.rm = TRUE),
    min_growth = min(growth, na.rm = TRUE),
    max_growth = max(growth, na.rm = TRUE),
    sd_growth = sd(growth, na.rm = TRUE),
    range_growth = max_growth - min_growth,
    cv_growth = sd_growth / abs(mean_growth),  
    .groups = 'drop'
  ) |>
  arrange(desc(sd_growth))

# Summary statistics 
stats_table <- summary_stats |>
  select(department, mean_growth, sd_growth, min_growth, max_growth, range_growth) |>
  mutate(across(where(is.numeric), ~ round(., 2)))

# Identify the most volatile departments (using SD as criteria)
volatile_departments <- summary_stats |> 
  slice_max(order_by = sd_growth, n = 3) |> 
  pull(department)

# Prepare the data for plotting
highlight_data <- tidy_data |> 
  filter(department %in% volatile_departments)

5. Visualization Parameters

Show code
# Get the departments in the correct order (by volatility)
volatile_departments <- summary_stats |> 
  slice_max(order_by = sd_growth, n = 3) |> 
  pull(department)

# Create a categorical palette for three volatile departments
cat_colors <- c(
  "grocery" = "#0F62FE",   
  "hardware" = "#FF7EB6",  
  "toys" = "#6929C4"        
)

# Get the rest of the departments
other_departments <- setdiff(unique(tidy_data$department), volatile_departments)

# Create a palette for all departments (both highlighted and non-highlighted)
all_dept_colors <- c(cat_colors, 
                     setNames(rep("gray80", length(other_departments)), 
                              other_departments))

# Colors theme function
colors <- get_theme_colors(
  palette = all_dept_colors
)

### |-  titles and caption ----
title_text <- str_glue("Retail Department Performance Analysis: Growth and Volatility")
subtitle_text <- str_glue("Understanding stability patterns and growth trajectories across retail categories\n(Jan 2024 - Mar 2025)")

# Format the names with colors for the descriptive title
grocery_color <- cat_colors["grocery"]
hardware_color <- cat_colors["hardware"]
toys_color <- cat_colors["toys"]

# P1 chart title
rich_title <- glue::glue(
  "<span style='color:{grocery_color}'>Grocery</span>, ",
  "<span style='color:{hardware_color}'>Hardware</span>, and ",
  "<span style='color:{toys_color}'>Toys</span> are the three most volatile departments"
)

# P2 chart title
rich_title_p2 <- glue::glue(
  "<span style='color:{grocery_color}'>Grocery</span> shows highest volatility despite below-average growth"
)

# Create caption
caption_text <- create_swd_exe_caption(
  year = 2025,
  month = "Mar",
  source_text =  "Let's Practice! Exercise 5.6"
)

### |-  fonts ----
setup_fonts()
fonts <- get_font_families()

### |-  plot theme ----

# Start with base theme
base_theme <- create_base_theme(colors)

# Add weekly-specific theme elements
weekly_theme <- extend_weekly_theme(
  base_theme,
  theme(
    # Text styling 
    plot.title = element_text(face = "bold", family = fonts$title, color = colors$title, size = rel(1.14), margin = margin(b = 10)),
    plot.subtitle = element_text(family = fonts$subtitle, color = colors$text, size = rel(0.78), margin = margin(b = 20)),
    
    # Axis elements
    axis.title = element_text(color = colors$text, size = rel(0.8)),
    axis.text = element_text(color = colors$text, size = rel(0.7)),
    
    # Grid elements
    panel.grid.minor = element_blank(),
    panel.grid.major = element_line(color = "grey95", linewidth = 0.1),
    
    # Legend elements
    legend.position = "plot",
    legend.title = element_text(family = fonts$text, size = rel(0.8)),
    legend.text = element_text(family = fonts$text, size = rel(0.7)),
    
    # Plot margins 
    plot.margin = margin(t = 10, r = 10, b = 10, l = 10),
  )
)

# Set theme
theme_set(weekly_theme)

6. Plot

Show code
# P1. Line Chart ----
p1 <- ggplot() +
  # Geoms
  geom_hline(
    yintercept = 0, color = "gray30", alpha = 0.5, linewidth = 0.5
  ) +
  geom_line(data = tidy_data, 
            aes(x = date, y = growth, group = department), 
            color = "gray", linewidth = 0.2, alpha = 0.8
  ) +
  geom_line(data = highlight_data,           # Highlighted volatile departments
            aes(x = date, y = growth, color = department), 
            linewidth = 1.2
  ) +
  geom_point(data = highlight_data,
             aes(x = date, y = growth, color = department),
             size = 2
  ) +
  geom_text_repel(
    data = highlight_data |> 
      group_by(department) |> 
      filter(date == max(date)),
    aes(x = date, y = growth, label = str_to_title(department), color = department),
    hjust = -0.2, size = 4, fontface = "bold",
    direction = "y", segment.color = "gray50",
    box.padding = 0.5, point.padding = 0.3
  ) +
  
  # Scales
  scale_x_date(
    date_breaks = "2 months",
    date_labels = "%b\n%Y",
    expand = expansion(mult = c(0.01, 0.1))  
  ) +
  scale_y_continuous(
    labels = function(x) paste0(x, "%"),
    breaks = seq(-3, 7, by = 1)
  ) +
  scale_color_manual(values = cat_colors) +  
  # Labs
  labs(
    title = rich_title,
    subtitle = "Department Year-over-Year Growth Trends",
    y = NULL,
    x = NULL,
  ) +
  # Theme
  theme(
    panel.grid.major.y = element_line(color = "gray90"),
    plot.title = element_markdown(
      size = rel(1.1), 
      family = fonts$title,
      face = "bold",
      margin = margin(b = 10)
    )
  )

# P2. Scatter Plot ----
p2 <- ggplot(summary_stats,
             aes(x = mean_growth, y = sd_growth, label = department)) +
  # Geoms
  geom_point(
    aes(color = department, size = range_growth),
    alpha = 0.8
  ) +
  geom_text_repel(
    size = 3.5,
    box.padding = 0.5,
    max.overlaps = 15,
    segment.color = "gray70"
  ) +
  # Scales
  scale_color_manual(
    values = all_dept_colors  # applies the same colors to all departments
  ) +
  scale_size_continuous(range = c(2, 6)) +
  labs(
    title = rich_title_p2,
    subtitle = "Department Performance: Comparing Growth and Volatility",
    x = "Mean Growth Rate (%)",
    y = NULL   
  ) +
  # Theme
  theme(
    plot.title = element_markdown(
      size = rel(1.1), 
      family = fonts$title,
      face = "bold",
      margin = margin(b = 10)
    ),
    panel.grid.minor = element_blank(),
    panel.grid.major = element_line(color = "grey70", linewidth = 0.1),
  )

# Combined Plot -----
combined_plot <- (p1 / p2) +
  plot_layout(heights = c(1, 1))   

combined_plot <- combined_plot +
  plot_annotation(
    title = title_text,
    subtitle = subtitle_text,
    caption = caption_text,
    theme = theme(
      plot.title = element_text( 
        size = rel(1.4),
        family = fonts$title,
        face = "bold",
        color = colors$title,
        lineheight = 1.1,
        margin = margin(t = 5, b = 5)
      ),
      plot.subtitle = element_text(
        size = rel(0.95),  
        family = fonts$subtitle,
        color = colors$subtitle,
        lineheight = 1.2,
        margin = margin(t = 5, b = 10)
      ),
      plot.caption = element_markdown(
        size   = rel(0.65),
        family = fonts$caption,
        color  = colors$caption,
        hjust  = 0.5,
        margin = margin(t = 10)
      ),
      plot.margin = margin(t = 20, r = 10, b = 20, l = 10),
    ))

7. Save

Show code
### |-  plot image ----  

source(here::here("R/image_utils.R"))
save_plot_patchwork(
  combined_plot, 
  type = 'swd', 
  year = 2025, 
  month = 03, 
  exercise = 056,
  width = 8, 
  height = 10
  )

8. Session Info

Expand for Session Info
R version 4.4.1 (2024-06-14 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 22631)

Matrix products: default


locale:
[1] LC_COLLATE=English_United States.utf8 
[2] LC_CTYPE=English_United States.utf8   
[3] LC_MONETARY=English_United States.utf8
[4] LC_NUMERIC=C                          
[5] LC_TIME=English_United States.utf8    

time zone: America/New_York
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices datasets  utils     methods   base     

other attached packages:
 [1] patchwork_1.3.0 ggrepel_0.9.6   camcorder_0.1.0 janitor_2.2.0  
 [5] here_1.0.1      glue_1.8.0      scales_1.3.0    showtext_0.9-7 
 [9] showtextdb_3.0  sysfonts_0.8.9  ggtext_0.1.2    lubridate_1.9.3
[13] forcats_1.0.0   stringr_1.5.1   dplyr_1.1.4     purrr_1.0.2    
[17] readr_2.1.5     tidyr_1.3.1     tibble_3.2.1    ggplot2_3.5.1  
[21] tidyverse_2.0.0 pacman_0.5.1   

loaded via a namespace (and not attached):
 [1] gtable_0.3.6       xfun_0.49          htmlwidgets_1.6.4  tzdb_0.4.0        
 [5] yulab.utils_0.1.8  vctrs_0.6.5        tools_4.4.0        generics_0.1.3    
 [9] curl_6.0.0         parallel_4.4.0     gifski_1.32.0-1    fansi_1.0.6       
[13] pkgconfig_2.0.3    ggplotify_0.1.2    lifecycle_1.0.4    compiler_4.4.0    
[17] farver_2.1.2       munsell_0.5.1      codetools_0.2-20   snakecase_0.11.1  
[21] htmltools_0.5.8.1  yaml_2.3.10        crayon_1.5.3       pillar_1.9.0      
[25] magick_2.8.5       commonmark_1.9.2   tidyselect_1.2.1   digest_0.6.37     
[29] stringi_1.8.4      labeling_0.4.3     rsvg_2.6.1         rprojroot_2.0.4   
[33] fastmap_1.2.0      grid_4.4.0         colorspace_2.1-1   cli_3.6.3         
[37] magrittr_2.0.3     utf8_1.2.4         withr_3.0.2        bit64_4.5.2       
[41] timechange_0.3.0   rmarkdown_2.29     bit_4.5.0          hms_1.1.3         
[45] evaluate_1.0.1     knitr_1.49         markdown_1.13      gridGraphics_0.5-1
[49] rlang_1.1.4        gridtext_0.1.5     Rcpp_1.0.13-1      xml2_1.3.6        
[53] renv_1.0.3         svglite_2.1.3      rstudioapi_0.17.1  vroom_1.6.5       
[57] jsonlite_1.8.9     R6_2.5.1           fs_1.6.5           systemfonts_1.1.0 

9. GitHub Repository

Expand for GitHub Repo

The complete code for this analysis is available in swd_2025_02-Ex_056.qmd. For the full repository, click here.

10. References

Expand for References

Data Sources:

  1. Data Sources:

    • Storytelling with Data Exercise | resist the temptation to show all the data: Download the data
Back to top
Source Code
---
title: "Retail Department Performance Analysis: Growth and Volatility"
subtitle: "Understanding stability patterns and growth trajectories across retail categories (Jan 2024 - Mar 2025)"
description: "An in-depth analysis of retail department performance examining growth rates and volatility across multiple categories. This visualization highlights stability patterns and identifies departments with unpredictable performance, providing valuable insights for inventory management and business planning."
author: "Steven Ponce"
date: "2025-03-19"
categories: ["SWDchallenge", "Exercise", "Data Visualization", "R Programming", "2025"]
date-modified: last-modified
tags: [
  "retail analysis", "YoY growth", "volatility metrics", 
"ggplot2", "time series visualization", "scatter plot", 
"performance dashboard", "department comparison", "standard deviation", "business intelligence"
]
image: "thumbnails/swd_2025_03-Ex_0056.png"
format:
  html:
    toc: true
    toc-depth: 5
    code-link: true
    code-fold: true
    code-tools: true
    code-summary: "Show code"
    self-contained: true
editor_options: 
  chunk_output_type: inline
execute: 
  freeze: true                                          
  cache: true                                                   
  error: false
  message: false
  warning: false
  eval: true
# share:
#   permalink: "https://stevenponce.netlify.app/data_visualizations/SWD Challenge/2025/swd_2025_02-Ex_056.html" 
#   description: "Explore my latest data visualization project analyzing retail department growth patterns and volatility. This #SWDchallenge entry examines performance trends across multiple retail categories, highlighting stability patterns and outliers from Jan 2024 to Mar 2025. #DataViz #RetailAnalytics #ggplot2"
#   linkedin: true
#   twitter: true
#   email: true
---

**Update**: This post has been updated based on valuable feedback from the Antti Rask. The changes include:

- Implemented a categorical color palette to create better visual distinction between the three highlighted departments 
- Applied consistent colors across both visualizations to help readers connect departments between charts 
- Created more descriptive titles with colored department names using the ggtext package to enhance immediate comprehension 
- Revised the chart subtitles to better explain the key insights from each visualization 
- Removed redundant axis titles while maintaining necessary labels for clarity 
- Added specific emphasis on Grocery's unique position as highly volatile despite lower growth 
- Improved the scatter plot to better highlight the relationship between growth rates and stability patterns 
- Enhanced overall visual hierarchy by using color more purposefully throughout the visualization


### Original

The goal of this month's Storytelling with Data exercise is toresist the temptation to show all the data.

![Original chart](https://stwd-prod-static-back.s3.amazonaws.com/media/exercise056_before.png){#fig-1}


Additional information can be found [HERE](https://community.storytellingwithdata.com/exercises/resist-the-temptation-to-show-all-the-data)


### Makeover

![Retail department performance visualization with two charts. Top chart shows year-over-year growth trends from Jan 2024 to Mar 2025, highlighting the three most volatile departments (Grocery, Hardware, and Toys) with Grocery showing the most dramatic fluctuations. Bottom chart plots standard deviation against mean growth rate for all departments, revealing that departments with higher volatility (like Grocery) don't necessarily have the highest average growth rates.](swd_2025_03-Ex_0056.png){#fig-1}


### <mark> __Steps to Create this Graphic__ </mark>

#### 1. Load Packages & Setup 

```{r}
#| label: load

if (!require("pacman")) install.packages("pacman")
pacman::p_load(
  tidyverse,         # Easily Install and Load the 'Tidyverse'
  ggtext,            # Improved Text Rendering Support for 'ggplot2'
  showtext,          # Using Fonts More Easily in R Graphs
  scales,            # Scale Functions for Visualization
  glue,              # Interpreted String Literals
  here,              # A Simpler Way to Find Your Files
  janitor,           # Simple Tools for Examining and Cleaning Dirty Data
  camcorder,         # Record Your Plot History,
  ggrepel,           # Automatically Position Non-Overlapping Text Labels with 'ggplot2'
  patchwork          # The Composer of Plots # The Composer of Plots # The Composer of Plots
) 

### |- figure size ---- 
gg_record( 
  dir    = here::here("temp_plots"), 
  device = "png",
  width  = 8,
  height = 10,
  units  = "in",
  dpi    = 320)
# Source utility functions
suppressMessages(source(here::here("R/utils/fonts.R")))
source(here::here("R/utils/social_icons.R"))
source(here::here("R/utils/image_utils.R"))
source(here::here("R/themes/base_theme.R"))
```

#### 2. Read in the Data 

```{r}
#| label: read

raw_data <- read_csv(
  here::here("data/swdexercise056.csv")
  ) |> 
  clean_names() 
```

#### 3. Examine the Data

```{r}
#| label: examine
#| include: true
#| eval: true
#| results: 'hide'
#| warning: false

glimpse(raw_data)
```

#### 4. Tidy Data 

```{r}
#| label: tidy

tidy_data <- raw_data |>
  rename(month = column1, year = column2) |>
  mutate(
    date = as.Date(paste(year, month, "01", sep = "-"), 
                   format = "%Y-%b-%d")
  ) |>
  select(date, everything(), -month, -year) |>
  pivot_longer(
    cols = -date, 
    names_to = "department", 
    values_to = "growth"
  ) |> 
  mutate(
    year_month = format(date, "%b %Y"),
    quarter = paste0("Q", quarter(date)),
    year_quarter = paste(year(date), quarter, sep = "-")
  )

# Compute summary statistics for each department
summary_stats <- tidy_data |>
  group_by(department) |>
  summarise(
    mean_growth = mean(growth, na.rm = TRUE),
    min_growth = min(growth, na.rm = TRUE),
    max_growth = max(growth, na.rm = TRUE),
    sd_growth = sd(growth, na.rm = TRUE),
    range_growth = max_growth - min_growth,
    cv_growth = sd_growth / abs(mean_growth),  
    .groups = 'drop'
  ) |>
  arrange(desc(sd_growth))

# Summary statistics 
stats_table <- summary_stats |>
  select(department, mean_growth, sd_growth, min_growth, max_growth, range_growth) |>
  mutate(across(where(is.numeric), ~ round(., 2)))

# Identify the most volatile departments (using SD as criteria)
volatile_departments <- summary_stats |> 
  slice_max(order_by = sd_growth, n = 3) |> 
  pull(department)

# Prepare the data for plotting
highlight_data <- tidy_data |> 
  filter(department %in% volatile_departments)
```


#### 5. Visualization Parameters 

```{r}
#| label: params

# Get the departments in the correct order (by volatility)
volatile_departments <- summary_stats |> 
  slice_max(order_by = sd_growth, n = 3) |> 
  pull(department)

# Create a categorical palette for three volatile departments
cat_colors <- c(
  "grocery" = "#0F62FE",   
  "hardware" = "#FF7EB6",  
  "toys" = "#6929C4"        
)

# Get the rest of the departments
other_departments <- setdiff(unique(tidy_data$department), volatile_departments)

# Create a palette for all departments (both highlighted and non-highlighted)
all_dept_colors <- c(cat_colors, 
                     setNames(rep("gray80", length(other_departments)), 
                              other_departments))

# Colors theme function
colors <- get_theme_colors(
  palette = all_dept_colors
)

### |-  titles and caption ----
title_text <- str_glue("Retail Department Performance Analysis: Growth and Volatility")
subtitle_text <- str_glue("Understanding stability patterns and growth trajectories across retail categories\n(Jan 2024 - Mar 2025)")

# Format the names with colors for the descriptive title
grocery_color <- cat_colors["grocery"]
hardware_color <- cat_colors["hardware"]
toys_color <- cat_colors["toys"]

# P1 chart title
rich_title <- glue::glue(
  "<span style='color:{grocery_color}'>Grocery</span>, ",
  "<span style='color:{hardware_color}'>Hardware</span>, and ",
  "<span style='color:{toys_color}'>Toys</span> are the three most volatile departments"
)

# P2 chart title
rich_title_p2 <- glue::glue(
  "<span style='color:{grocery_color}'>Grocery</span> shows highest volatility despite below-average growth"
)

# Create caption
caption_text <- create_swd_exe_caption(
  year = 2025,
  month = "Mar",
  source_text =  "Let's Practice! Exercise 5.6"
)

### |-  fonts ----
setup_fonts()
fonts <- get_font_families()

### |-  plot theme ----

# Start with base theme
base_theme <- create_base_theme(colors)

# Add weekly-specific theme elements
weekly_theme <- extend_weekly_theme(
  base_theme,
  theme(
    # Text styling 
    plot.title = element_text(face = "bold", family = fonts$title, color = colors$title, size = rel(1.14), margin = margin(b = 10)),
    plot.subtitle = element_text(family = fonts$subtitle, color = colors$text, size = rel(0.78), margin = margin(b = 20)),
    
    # Axis elements
    axis.title = element_text(color = colors$text, size = rel(0.8)),
    axis.text = element_text(color = colors$text, size = rel(0.7)),
    
    # Grid elements
    panel.grid.minor = element_blank(),
    panel.grid.major = element_line(color = "grey95", linewidth = 0.1),
    
    # Legend elements
    legend.position = "plot",
    legend.title = element_text(family = fonts$text, size = rel(0.8)),
    legend.text = element_text(family = fonts$text, size = rel(0.7)),
    
    # Plot margins 
    plot.margin = margin(t = 10, r = 10, b = 10, l = 10),
  )
)

# Set theme
theme_set(weekly_theme)
```


#### 6. Plot

```{r}
#| label: plot

# P1. Line Chart ----
p1 <- ggplot() +
  # Geoms
  geom_hline(
    yintercept = 0, color = "gray30", alpha = 0.5, linewidth = 0.5
  ) +
  geom_line(data = tidy_data, 
            aes(x = date, y = growth, group = department), 
            color = "gray", linewidth = 0.2, alpha = 0.8
  ) +
  geom_line(data = highlight_data,           # Highlighted volatile departments
            aes(x = date, y = growth, color = department), 
            linewidth = 1.2
  ) +
  geom_point(data = highlight_data,
             aes(x = date, y = growth, color = department),
             size = 2
  ) +
  geom_text_repel(
    data = highlight_data |> 
      group_by(department) |> 
      filter(date == max(date)),
    aes(x = date, y = growth, label = str_to_title(department), color = department),
    hjust = -0.2, size = 4, fontface = "bold",
    direction = "y", segment.color = "gray50",
    box.padding = 0.5, point.padding = 0.3
  ) +
  
  # Scales
  scale_x_date(
    date_breaks = "2 months",
    date_labels = "%b\n%Y",
    expand = expansion(mult = c(0.01, 0.1))  
  ) +
  scale_y_continuous(
    labels = function(x) paste0(x, "%"),
    breaks = seq(-3, 7, by = 1)
  ) +
  scale_color_manual(values = cat_colors) +  
  # Labs
  labs(
    title = rich_title,
    subtitle = "Department Year-over-Year Growth Trends",
    y = NULL,
    x = NULL,
  ) +
  # Theme
  theme(
    panel.grid.major.y = element_line(color = "gray90"),
    plot.title = element_markdown(
      size = rel(1.1), 
      family = fonts$title,
      face = "bold",
      margin = margin(b = 10)
    )
  )

# P2. Scatter Plot ----
p2 <- ggplot(summary_stats,
             aes(x = mean_growth, y = sd_growth, label = department)) +
  # Geoms
  geom_point(
    aes(color = department, size = range_growth),
    alpha = 0.8
  ) +
  geom_text_repel(
    size = 3.5,
    box.padding = 0.5,
    max.overlaps = 15,
    segment.color = "gray70"
  ) +
  # Scales
  scale_color_manual(
    values = all_dept_colors  # applies the same colors to all departments
  ) +
  scale_size_continuous(range = c(2, 6)) +
  labs(
    title = rich_title_p2,
    subtitle = "Department Performance: Comparing Growth and Volatility",
    x = "Mean Growth Rate (%)",
    y = NULL   
  ) +
  # Theme
  theme(
    plot.title = element_markdown(
      size = rel(1.1), 
      family = fonts$title,
      face = "bold",
      margin = margin(b = 10)
    ),
    panel.grid.minor = element_blank(),
    panel.grid.major = element_line(color = "grey70", linewidth = 0.1),
  )

# Combined Plot -----
combined_plot <- (p1 / p2) +
  plot_layout(heights = c(1, 1))   

combined_plot <- combined_plot +
  plot_annotation(
    title = title_text,
    subtitle = subtitle_text,
    caption = caption_text,
    theme = theme(
      plot.title = element_text( 
        size = rel(1.4),
        family = fonts$title,
        face = "bold",
        color = colors$title,
        lineheight = 1.1,
        margin = margin(t = 5, b = 5)
      ),
      plot.subtitle = element_text(
        size = rel(0.95),  
        family = fonts$subtitle,
        color = colors$subtitle,
        lineheight = 1.2,
        margin = margin(t = 5, b = 10)
      ),
      plot.caption = element_markdown(
        size   = rel(0.65),
        family = fonts$caption,
        color  = colors$caption,
        hjust  = 0.5,
        margin = margin(t = 10)
      ),
      plot.margin = margin(t = 20, r = 10, b = 20, l = 10),
    ))
```

#### 7. Save

```{r}
#| label: save

### |-  plot image ----  

source(here::here("R/image_utils.R"))
save_plot_patchwork(
  combined_plot, 
  type = 'swd', 
  year = 2025, 
  month = 03, 
  exercise = 056,
  width = 8, 
  height = 10
  )
```


#### 8. Session Info

::: {.callout-tip collapse="true"}
##### Expand for Session Info

```{r, echo = FALSE}
#| eval: true
#| warning: false

sessionInfo()
```
:::


#### 9. GitHub Repository
::: {.callout-tip collapse="true"}
##### Expand for GitHub Repo
 
The complete code for this analysis is available in [`swd_2025_02-Ex_056.qmd`](https://github.com/poncest/personal-website/tree/master/data_visualizations/SWD%20Challenge/2025/swd_2025_02-Ex_056.qmd).
For the full repository, [click here](https://github.com/poncest/personal-website/).
:::

#### 10. References
::: {.callout-tip collapse="true"}
##### Expand for References
 
Data Sources:

1. Data Sources:

   - Storytelling with Data Exercise | resist the temptation to show all the data: [Download the data](https://community.storytellingwithdata.com/exercises/resist-the-temptation-to-show-all-the-data)


:::

© 2024 Steven Ponce

Source Issues